package com.flame.shapeville.controller.task;

import com.flame.shapeville.bean.Exercise;
import com.flame.shapeville.component.task2.Angle;
import com.flame.shapeville.util.Utils;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Controller for Task 2, which focuses on angle classification.
 * <p>
 * Users identify angles by selecting a degree value and submitting their guess.
 * The application supports five types of angles: acute, right, obtuse, straight, and reflex (reflect).
 *
 * @author Flame Dev Team
 * @version 1.0
 * @since 2025-05-13
 */
public class Task2Controller extends JfTaskController {
    /**
     * ComboBox UI component used to select an angle in degrees.
     */
    @FXML
    private ComboBox<String> angle;

    /**
     * Minimum valid angle value available for selection.
     */
    private String minValidAngle = "10";

    /**
     * Set of valid angles based on previously answered exercises.
     */
    private LinkedHashSet<String> validAngles = new LinkedHashSet<>();

    /**
     * Initializes the controller by setting up UI components and loading the current exercise.
     */
    @Override
    public void init() {
        super.init();
        setExercise();
        flushSwitchButton();
        initNotice();
        initAngleBox();
        initInput();
    }

    /**
     * Sets up input validation and event listeners for user answer submission.
     */
    private void initInput() {
        input.setOnKeyPressed(event -> {
            if (event.getCode() == javafx.scene.input.KeyCode.ENTER) {
                submit();
            }
        });
        Utils.addClickOutsideToLoseFocus(input, event -> input.getParent().requestFocus());
    }

    /**
     * Sets up combo box for angle selection with filtering and validation.
     */
    private void initAngleBox() {
        angle.setEditable(true);
        angle.getItems().clear();
        setValidAngles();
        ObservableList<String> items = angle.getItems();
        items.clear();
        items.addAll(validAngles);

        // Text field input listener for numeric validation
        angle.getEditor().textProperty().addListener((observable, oldValue, newValue) -> {
            if (!newValue.matches("\\d*")) {
                angle.setValue(oldValue);
            }
        });

        // Selection listener to handle valid angle updates
        angle.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue == null || newValue.isEmpty()) {
                return;
            }
            newValue = setAngle(newValue);
            angle.setValue(newValue);
            angle.setDisable(true);
        });

        // Focus listener to validate and update angle after editing
        angle.getEditor().focusedProperty().addListener((observable, oldValue, newValue) -> {
            String value = angle.getValue();
            if (value == null || value.isEmpty()) {
                return;
            }
            value = setAngle(angle.getValue().trim());
            angle.setValue(value);
            angle.setDisable(true);
        });

        // Add click outside handler to lose focus
        Utils.addClickOutsideToLoseFocus(angle, event -> angle.getParent().requestFocus());
    }

    /**
     * Filters out invalid angles based on previous answers.
     * <p>
     * Invalid angles are determined by the type of angle already answered in other exercises.
     */
    private void setValidAngles() {
        validAngles.clear();
        for (int i = 1; i < 36; i++) {
            validAngles.add(i * 10 + "");
        }

        Set<String> invalidAngles = new LinkedHashSet<>();
        for (Exercise exercise : exercises) {
            String answer = exercise.getAnswer();
            if (answer != null) {
                switch (answer) {
                    case "acute":
                        for (int i = 1; i < 9; i++) {
                            invalidAngles.add(i * 10 + "");
                        }
                        break;
                    case "right":
                        invalidAngles.add("90");
                        break;
                    case "obtuse":
                        for (int i = 9; i < 18; i++) {
                            invalidAngles.add(i * 10 + "");
                        }
                        break;
                    case "straight":
                        invalidAngles.add("180");
                        break;
                    case "reflect":
                        for (int i = 19; i < 36; i++) {
                            invalidAngles.add(i * 10 + "");
                        }
                        break;
                }
            }
        }

        validAngles.removeAll(invalidAngles);
        minValidAngle = validAngles.getFirst();
    }

    /**
     * Validates and normalizes the selected angle string.
     * <p>
     * Converts the value to nearest multiple of 10 and ensures it's within valid range.
     *
     * @param angleStr the selected angle as a string
     * @return normalized angle string
     */
    private String setAngle(String angleStr) {
        int angleInt = Integer.parseInt(angleStr);
        angleInt = angleInt / 10 * 10;
        if (angleInt >= 360) {
            angleInt -= 360;
        }
        angleStr = angleInt + "";
        if (!validAngles.contains(angleStr)) {
            angleStr = minValidAngle;
        }
        curExercise.addParam("angle", angleInt);
        setAnswer();
        loadShape();
        return angleStr;
    }

    /**
     * Repopulates the combo box with updated valid angles.
     */
    private void flushComboBox() {
        angle.getItems().clear();
        setValidAngles();
        angle.getItems().addAll(validAngles);
    }

    /**
     * Sets the expected answer based on the selected angle value.
     * <p>
     * Uses standard mathematical definitions:
     * - Acute: less than 90°
     * - Right: exactly 90°
     * - Obtuse: between 90° and 180°
     * - Straight: exactly 180°
     * - Reflex: between 180° and 360°
     */
    private void setAnswer() {
        Integer angleInt = (Integer) curExercise.getParam("angle");
        if (angleInt < 90) {
            curExercise.setAnswer("acute");
        } else if (angleInt == 90) {
            curExercise.setAnswer("right");
        } else if (angleInt < 180) {
            curExercise.setAnswer("obtuse");
        } else if (angleInt == 180) {
            curExercise.setAnswer("straight");
        } else if (angleInt < 360) {
            curExercise.setAnswer("reflect");
        }
    }

    /**
     * Sets the current exercise based on pointer index and updates the UI accordingly.
     */
    @Override
    void setExercise() {
        curExercise = exercises.get(exPointer);
        if (curExercise.getAnswer() == null) {
            question.setText("Please choice an angle");
            shape.getChildren().clear();
            angle.setDisable(false);
        } else {
            question.setText("What type of angle is this?");
            angle.setValue(curExercise.getParam("angle") + "");
            angle.setDisable(true);
            loadShape();
        }
        no.setText(curExercise.getNo() + "");
        flushInput();
    }

    /**
     * Handles user submission of an answer.
     */
    @Override
    void submit() {
        if (!angle.isDisable() || curExercise.getAnswer() == null) {
            return;
        }
        String answer = input.getText();
        if (answer == null || answer.isEmpty()) {
            return;
        }
        answer = answer.trim().toLowerCase().split("\\s+")[0];
        answerAnimationAndSave(answer);
    }

    /**
     * Updates input field state based on whether the current exercise is completed.
     */
    @Override
    void flushInput() {
        if (curExercise.getAnswer() != null) {
            angle.setDisable(true);
            angle.setValue(curExercise.getParam("angle") + "");
        } else {
            flushComboBox();
            angle.setValue("");
            angle.setDisable(false);
        }
        if (curExercise.getCompleted()) {
            input.setText(curExercise.getAnswer());
            input.setDisable(true);
            submit.setDisable(true);
        } else {
            input.clear();
            input.setDisable(false);
            submit.setDisable(false);
        }
    }

    /**
     * Loads the angle visualization into the UI.
     */
    private void loadShape() {
        shape.getChildren().clear();
        Angle angleObj = new Angle(100, (Integer) curExercise.getParam("angle"));
        shape.getChildren().add(angleObj);
    }
}
